package com.google.code.irouter.utils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.SortDirection;
import com.google.appengine.api.datastore.Transaction;
import com.google.code.irouter.model.Model;


public final class ModelUtil {
	
	private static DatastoreService DATASTORE;
	
	public static DatastoreService getDatastore() {
		return DATASTORE;
	}

	public static void setDatastore(Object datastore) {
		ModelUtil.DATASTORE = (DatastoreService) datastore;
	}

	public static Model save(Model model) {
		Transaction transaction = DATASTORE.beginTransaction();
		try {
			Key key = DATASTORE.put(model.getEntity());
			model.setKey(key);
		    transaction.commit();
		} finally {
		    if (transaction.isActive()) {
		    	transaction.rollback();
		    }
		}
		return model;
	}

	public static void update(Model model) {
		Transaction transaction = DATASTORE.beginTransaction();
		try {
			Entity entity = model.getEntity();
			DATASTORE.put(entity);
		    transaction.commit();
		} finally {
		    if (transaction.isActive()) {
		    	transaction.rollback();
		    }
		}
	}
	
	public static void remove(Iterable<Key > keys) {
		DATASTORE.delete(keys);
	}
	
	public static void remove(Model model) {
		Transaction transaction = DATASTORE.beginTransaction();
		try {
			DATASTORE.delete(model.getKey());
		    transaction.commit();
		} finally {
		    if (transaction.isActive()) {
		    	transaction.rollback();
		    }
		}
	}
	
	public static List<Model> select(Class<? extends Model> cls) {
		Query query = new Query(cls.getSimpleName());
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		PreparedQuery preparedQuery = datastore.prepare(query);
		Iterator<Entity> entityIterator = preparedQuery.asIterator();
		List<Model> modelList = new ArrayList<Model>();
		while(entityIterator.hasNext()) {
			Entity entity = entityIterator.next();
			try {
				Model model = cls.newInstance();
				if(model.parse(entity))
					modelList.add(model);
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		return modelList;
	}
	
	public static List<Model> selectByOrder(Class<? extends Model> cls, Map<String, SortDirection> order) {
		Query query = new Query(cls.getSimpleName());
		if(order != null) {
			Set<String> orderSet = order.keySet();
			for(String key : orderSet) {
				SortDirection direction = order.get(key);
				query.addSort(key, direction);
			}
		}
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		PreparedQuery preparedQuery = datastore.prepare(query);
		Iterator<Entity> entityIterator = preparedQuery.asIterator();
		List<Model> modelList = new ArrayList<Model>();
		while(entityIterator.hasNext()) {
			Entity entity = entityIterator.next();
			try {
				Model model = cls.newInstance();
				if(model.parse(entity))
					modelList.add(model);
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		return modelList;
	}
	
	public static List<Model> select(Class<? extends Model> cls, Key key) {
		List<Model> modelList = new ArrayList<Model>();
		Entity entity;
		try {
			entity = DATASTORE.get(key);
			Model model = cls.newInstance();
			model.parse(entity);
			modelList.add(model);
		} catch (EntityNotFoundException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}		
		return modelList;
	}
	
	public static List<Model> selectNot(Class<? extends Model> cls, Map<String, Object> map) {
		Query query = new Query(cls.getSimpleName());
		Set<String> keySet = map.keySet();
		for(String key : keySet) {
			Object value = map.get(key);
			query.addFilter(key, FilterOperator.NOT_EQUAL, value);
		}	
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		PreparedQuery preparedQuery = datastore.prepare(query);
		Iterator<Entity> entityIterator = preparedQuery.asIterator();
		List<Model> modelList = new ArrayList<Model>();
		while(entityIterator.hasNext()) {
			Entity entity = entityIterator.next();
			try {
				Model model = cls.newInstance();
				if(model.parse(entity))
					modelList.add(model);
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		return modelList;
	}
	
	public static List<Model> select(Class<? extends Model> cls, Map<String, Object> map) {
		Query query = new Query(cls.getSimpleName());
		Set<String> keySet = map.keySet();
		for(String key : keySet) {
			Object value = map.get(key);
			if(Collection.class.isInstance(value)) {
				query.addFilter(key, FilterOperator.IN, value);
			} else {
				query.addFilter(key, FilterOperator.EQUAL, value);
			}
		}	
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		PreparedQuery preparedQuery = datastore.prepare(query);
		Iterator<Entity> entityIterator = preparedQuery.asIterator();
		List<Model> modelList = new ArrayList<Model>();
		while(entityIterator.hasNext()) {
			Entity entity = entityIterator.next();
			try {
				Model model = cls.newInstance();
				if(model.parse(entity))
					modelList.add(model);
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		return modelList;
	}

	public static List<Model> selectByOrder(Class<? extends Model> cls, Map<String, Object> map, Map<String, SortDirection> order) {
		Query query = new Query(cls.getSimpleName());
		Set<String> keySet = map.keySet();
		for(String key : keySet) {
			Object value = map.get(key);
			if(Collection.class.isInstance(value)) {
				query.addFilter(key, FilterOperator.IN, value);
			} else {
				query.addFilter(key, FilterOperator.EQUAL, value);
			}
		}
		if(order != null) {
			Set<String> orderSet = order.keySet();
			for(String key : orderSet) {
				SortDirection direction = order.get(key);
				query.addSort(key, direction);
			}
		}		
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		PreparedQuery preparedQuery = datastore.prepare(query);
		Iterator<Entity> entityIterator = preparedQuery.asIterator();
		List<Model> modelList = new ArrayList<Model>();
		while(entityIterator.hasNext()) {
			Entity entity = entityIterator.next();
			try {
				Model model = cls.newInstance();
				if(model.parse(entity))
					modelList.add(model);
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		return modelList;
	}
	
	public static List<Model> selectByOrderWithLimit(Class<? extends Model> cls, Map<String, Object> map, Map<String, SortDirection> order, int offset, int limit) {
		Query query = new Query(cls.getSimpleName());
		Set<String> keySet = map.keySet();
		for(String key : keySet) {
			Object value = map.get(key);
			if(Collection.class.isInstance(value)) {
				query.addFilter(key, FilterOperator.IN, value);
			} else {
				query.addFilter(key, FilterOperator.EQUAL, value);
			}
		}
		if(order != null) {
			Set<String> orderSet = order.keySet();
			for(String key : orderSet) {
				SortDirection direction = order.get(key);
				query.addSort(key, direction);
			}
		}		
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		PreparedQuery preparedQuery = datastore.prepare(query);		
		Iterator<Entity> entityIterator = 
			preparedQuery.asIterator(
					FetchOptions.Builder.withLimit(limit).offset(offset)
			);
		List<Model> modelList = new ArrayList<Model>();
		while(entityIterator.hasNext()) {
			Entity entity = entityIterator.next();
			try {
				Model model = cls.newInstance();
				if(model.parse(entity))
					modelList.add(model);
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		return modelList;
	}

}
